/*
 *   The contents of this file are subject to the Mozilla Public License
 *   Version 1.1 (the "License"); you may not use this file except in
 *   compliance with the License. You may obtain a copy of the License at
 *   http://www.mozilla.org/MPL/
 *
 *   Software distributed under the License is distributed on an "AS IS"
 *   basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 *   License for the specific language governing rights and limitations
 *   under the License.
 *
 *   The Original Code is Matra - the DTD Parser.
 *
 *   The Initial Developer of the Original Code is Conrad S Roche.
 *   Portions created by Conrad S Roche are Copyright (C) Conrad 
 *   S Roche. All Rights Reserved.
 *
 *   Alternatively, the contents of this file may be used under the terms
 *   of the GNU GENERAL PUBLIC LICENSE Version 2 or any later version
 *   (the  "[GPL] License"), in which case the
 *   provisions of GPL License are applicable instead of those
 *   above.  If you wish to allow use of your version of this file only
 *   under the terms of the GPL License and not to allow others to use
 *   your version of this file under the MPL, indicate your decision by
 *   deleting  the provisions above and replace  them with the notice and
 *   other provisions required by the GPL License.  If you do not delete
 *   the provisions above, a recipient may use your version of this file
 *   under either the MPL or the GPL License."
 *
 *   [NOTE: The text of this Exhibit A may differ slightly from the text of
 *   the notices in the Source Code files of the Original Code. You should
 *   use the text of this Exhibit A rather than the text found in the
 *   Original Code Source Code for Your Modifications.]
 *
 * Created: Conrad S Roche <derupe at users.sourceforge.net>,  25-Jul-2000
 */

package com.conradroche.matra.decl;

import java.util.*;

import com.conradroche.matra.data.*;
import com.conradroche.matra.exception.*;


/**
 * Class to hold an Element Type declaration.
 * 
 * @author: Conrad Roche
 */
public class ElementType {

	private String[] parentNames;
	private String[] childrenNames;
	private String[] attributeNames;

	private AttList attributeList;

	private String name;
	private com.conradroche.matra.decl.ElementType[] children;
	private java.lang.String contentSpec;

	private Hashtable optionality; //the optionality of the children

	private boolean hasEmptyContentSpec = false;
	
	private boolean loaded = false;
	
	private static final String ANY = "ANY";
	private static final String EMPTY = "EMPTY";
	
/**
 * Element constructor comment.
 */
public ElementType() {
	super();
}
/**
 * ElementType constructor.
 * 
 * @param content The element type declaration.
 * 
 * @throws DTDException if syntax error is encountered.
 */
public ElementType(String content) throws DTDException {
	
/*
[45] elementdecl ::= '<!ELEMENT' S Name S contentspec S? '>' [VC: Unique Element Type
Declaration]
[46] contentspec ::= 'EMPTY' | 'ANY' | Mixed | children 
*/

	if(content == null) {
		return;
	}
	
	DTDData elemCont = new DTDData(content.trim());

	String eleName = elemCont.getNextToken();
	setName(eleName);

	String elemContentSpec = elemCont.getRemaining();
	if(elemContentSpec == null)
		throw new DTDException("Element content missing for Element " + eleName + ".");

	elemContentSpec = elemContentSpec.trim();
	DTDData contentspec = new DTDData(elemContentSpec);

	setContentSpec(contentspec.toString());
	
	//System.out.println("eleName = '" + eleName + "', contentspec = '" + contentSpec + "'");
	
	if( contentspec.toString().equals(EMPTY)) {
		//no children
		hasEmptyContentSpec = true;
		return;
	} else if( contentspec.toString().equals(ANY) ) {
		//no children
		return;
	}

/*
[47] children ::= (choice | seq) ('?' | '*' | '+')?
[48] cp ::= (Name | choice | seq) ('?' | '*' | '+')?
[49] choice ::= '(' S? cp ( S? '|' S? cp )* S? ')' [VC: Proper Group/PE Nesting]
[50] seq ::= '(' S? cp ( S? ',' S? cp )* S? ')' [VC: Proper Group/PE Nesting]

[51] Mixed ::= '(' S? '#PCDATA' (S? '|' S? Name)* S? ')*'
| '(' S? '#PCDATA' S? ')' 	[VC: Proper Group/PE Nesting]
[VC: No Duplicate Types]
*/

	//the content model for children/mixed should start with '('
	if(contentspec.getNextChar() != '(') {
		throw new DTDException("Invalid character (" + contentspec.getPrevChar() + ") encountered, while parsing element " + eleName + ", at location " + contentspec.getCurrentLocation() + " - was expecting '('.");
	}

	contentspec.skipWhiteSpace();
	char[] delimiters = { ' ', '\t', '\r', '\n', '|', ',', '(', ')'/*, '?', '*', '+'*/ };
	String child;
	Vector vChildren = new Vector();
	optionality = new Hashtable();

	//CR: FIXME: Take care of the 'ANY' model ...
	contentspec.skipChars(delimiters);
	while( !contentspec.endOfData() ) {
		child = contentspec.getNextToken(delimiters);
		if(child == null || child.trim().length() == 0 || child.equals("?") || child.equals("*") || child.equals("+"))
			continue;
			
		char lastChar = child.charAt(child.length() - 1);
		if(lastChar == '?' || lastChar == '*' || lastChar == '+') {
			child = child.substring(0, child.length() - 1);
			optionality.put(child, new Character(lastChar));
		}
		
		contentspec.skipChars(delimiters);
		vChildren.addElement(child);
	}

	int nChildren = vChildren.size();
	childrenNames = new String[nChildren];

	for(int i = 0; i < nChildren; i++) {
		childrenNames[i] = (String) vChildren.elementAt(i);
		//System.out.println("child[" + i + "] = " + childrenNames[i]);
	}

}
/**
 * Returns the attribute list for this
 * element type.
 * 
 * @return The attribute list.
 * 
 * @see #setAttList
 */
public AttList getAttList() {
	
	return attributeList;
}
/**
 * Returns the attribute names for 
 * the attributes of this element type.
 * 
 * @return The attribute names.
 * 
 * @see #setAttributeNames
 */
public String[] getAttributeNames() {
	return attributeNames;
}

/**
 * Returns the cardinality for a child
 * in the content spec of this element type.
 * 
 * @return The child's cardinality.
 * 
 * @param childName The name of the child element.
 */
public String getChildOptionality(String childName) {

	if(optionality == null)
		return "";
	
	Character opt;
	opt = (Character) optionality.get(childName);

	if(opt != null)
		return opt.toString();
		
	return "";
}

/**
 * Returns the child element types for this Element Type.
 * 
 * @return The child element types.
 * 
 * @see #setChildren
 */
public ElementType[] getChildren() {
	return children;
}

/**
 * Returns the names of the child element
 * types for this element type.
 * 
 * @return The names of the child element.
 * 
 * @see #setChildrenNames
 */
public String[] getChildrenNames() {
	return childrenNames;
}

/**
 * Returns the content spec for this 
 * element type.
 * 
 * @see #setContentSpec
 * 
 * @return The content spec.
 */
public java.lang.String getContentSpec() {
	return contentSpec;
}

/**
 * Returns the name of this element
 * type.
 * 
 * @return The element type name.
 * 
 * @see #setName
 */
public String getName() {
	return name;
}

/**
 * Returns the parent element names.
 * 
 * @return The parent element names.
 * 
 * @see #setParentNames
 */
public String[] getParentNames() {
	return parentNames;
}

/**
 * Checks if this element type has any children.
 *  
 * @return <code>true</code> if this element
 * 		type has any children; <code>false</code>
 * 		otherwise.
 */
public boolean hasChildren() {

	//CR: FIXME: Do this only after dealing with the "ANY" model at all the places ...
	if(getContentSpec().equals(ANY))
		return true;


	if(childrenNames == null || childrenNames.length == 0)
		return false;

	if(childrenNames.length == 1 && childrenNames[0].equals("#PCDATA"))
		return false;

	return true;
}

/**
 * Checks if the content model of this
 * element type is ANY.
 * 
 * @return <code>true</code> if the content
 * model is ANY; <code>false</code> otherwise.
 */
public boolean isAnyContentModel() {

	if(getContentSpec().equals(ANY)) {
		return true;
	}

	return false;
}

/**
 * Checks if the content model of this
 * element type is empty.
 * 
 * @return <code>true</code> if the content
 * model is empty; <code>false</code> otherwise.
 */
public boolean isEmptyContentModel() {

	return hasEmptyContentSpec;
}

/**
 * Loads the data for this element type.
 * 
 * @param elementList    The list of all element types
 * 			in this DTD.
 * @param AttributeList  The list of all attributes 
 * 			for this element type.
 */
public void load( Hashtable elementList, Hashtable AttributeList ) {

	if(loaded)
		return;

	loaded = true;
	
	setAttList( (AttList) AttributeList.get( getName() ) );

	if(getContentSpec().equals(ANY)) {
		int len = elementList.size();
		children = new ElementType[ len ];
		childrenNames = new String[ len ];

		Enumeration elements = elementList.elements();
		for(int i = 0; i < len; i++) {
			children[i] = (ElementType) elements.nextElement();
			childrenNames[i] = children[i].getName();
			children[i].load( elementList, AttributeList );
		}
		
		return;
	}
	
	if( !hasChildren() )
		return;

	children = new ElementType[ childrenNames.length ];
	
	for(int i = 0; i < childrenNames.length; i++) {
		children[i] = (ElementType) elementList.get( childrenNames[i] );

		if( children[i] == null )
			return;

		children[i].load( elementList, AttributeList );
	}

	//children = newChildren;
}

/**
 * Sets the attribute list for this elemen type.
 * 
 * @param attlist The attribute list.
 * 
 * @see #getAttList
 */
public void setAttList(AttList attlist) {

	attributeList = attlist;
}
/**
 * Sets the attribute names for this element type. 
 * 
 * @param newAttributeNames The attribute names.
 * 
 * @see #getAttributeNames
 */
public void setAttributeNames(String[] newAttributeNames) {
	attributeNames = newAttributeNames;
}
/**
 * Sets the child elements for this element type.
 * 
 * @param newChildren The child elements.
 * 
 * @see #getChildren
 */
public void setChildren(ElementType[] newChildren) {
	children = newChildren;
}
/**
 * Sets the child element names.
 * 
 * @param newChildrenNames The child element names.
 * 
 * @see #getChildrenNames
 */
public void setChildrenNames(String[] newChildrenNames) {
	childrenNames = newChildrenNames;
}
/**
 * Sets the content spec for this element type.
 * 
 * @see #getContentSpec
 * 
 * @param newContentSpec The content spec.
 */
private void setContentSpec(java.lang.String newContentSpec) {
	contentSpec = newContentSpec;
}
/**
 * Sets the name for this element type.
 * 
 * @param newName The element name.
 * 
 * @see #getName
 */
public void setName(String newName) {
	name = newName;
}
/**
 * Sets the parent element names.
 * 
 * @param newParentNames The parent element names.
 * 
 * @see #getParentNames
 */
public void setParentNames(String[] newParentNames) {
	parentNames = newParentNames;
}
/**
 * Returns a DTD String representation for
 * this element type.
 * 
 * @return DTD String for this element type.
 */
public String toString() {

	String str = "<!ELEMENT " + getName() + " ";

	//use shortcut!!!
	str += getContentSpec();
	str += ">";
/*
	if(childrenNames == null || childrenNames.length == 0) {
		str += "EMPTY>";
		return str;
	}

	str += "(";
	for(int i = 0; i < childrenNames.length - 1; i++) {
		str += childrenNames[i] + ", ";
	}
	str += childrenNames[childrenNames.length - 1] + ") >";
*/
	
	return str;
}
}

